/**
 * 
 */
package com.huxi.lang.local;

import static java.time.temporal.ChronoField.DAY_OF_MONTH;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.YEAR;

import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
import java.util.TimeZone;

/**
 * 日期操作工具类
 * 
 * @since 1.0
 * @version 1.0
 * @author jian.wu
 */
public abstract class DateUtils {

	/**
	 * 一秒单位换算为毫秒数后的数值
	 * 
	 * @since 1.0
	 */
	public static final int MILLISECONDS_PER_SECOND = 1000;
	/**
	 * 一分单位换算为毫秒数后的数值
	 * 
	 * @since 1.0
	 */
	public static final int MILLISECONDS_PER_MINUTE = 60_000;
	/**
	 * 一时单位换算为毫秒数后的数值
	 * 
	 * @since 1.0
	 */
	public static final int MILLISECONDS_PER_HOUR = 3600_000;
	/**
	 * 一天单位换算为毫秒数后的数值
	 * 
	 * @since 1.0
	 */
	public static final long MILLISECONDS_PER_DAY = 3600_000L * 24;

	/**
	 * 一分单位换算为秒数后的数值
	 * 
	 * @since 1.0
	 */
	public static final int SECONDS_PER_MINUTE = 60;
	/**
	 * 一时单位换算为秒数后的数值
	 * 
	 * @since 1.0
	 */
	public static final int SECONDS_PER_HOUR = 3600;
	/**
	 * 一天单位换算为秒数后的数值
	 * 
	 * @since 1.0
	 */
	public static final int SECONDS_PER_DAY = 3600 * 24;

	/**
	 * 默认时区对象
	 * 
	 * @since 1.0
	 */
	public static final TimeZone defaultTimeZone = TimeZone.getDefault();

	/**
	 * 默认时区ID
	 * 
	 * @since 1.0
	 */
	public static final ZoneId defaultZoneId = ZoneId.systemDefault();

	/**
	 * 默认时区偏移对象
	 * 
	 * @since 1.0
	 */
	public static final ZoneOffset defaultZoneOffset = toZoneOffset(defaultZoneId);

	/**
	 * 上海时区名称
	 * 
	 * @since 1.0
	 */
	public static final String cnZoneName = "Asia/Shanghai";

	/**
	 * 上海时区对象
	 * 
	 * @since 1.0
	 */
	public static final TimeZone cnTimeZone = TimeZone.getTimeZone(cnZoneName);

	/**
	 * 上海时区ID
	 * 
	 * @since 1.0
	 */
	public static final ZoneId cnZoneId = ZoneId.of(cnZoneName);

	/**
	 * 上海时区偏移对象
	 * 
	 * @since 1.0
	 */
	public static final ZoneOffset cnZoneOffset = toZoneOffset(cnZoneId);

	/**
	 * 默认年精度日期格式
	 * 
	 * @since 1.0
	 */
	public final static String DEFAULT_TO_YEAR_FORMAT = "yyyy";
	/**
	 * 默认月精度日期格式
	 * 
	 * @since 1.0
	 */
	public final static String DEFAULT_TO_MONTH_FORMAT = "yyyy-MM";
	/**
	 * 默认天精度日期格式
	 * 
	 * @since 1.0
	 */
	public final static String DEFAULT_TO_DAY_FORMAT = "yyyy-MM-dd";
	/**
	 * 默认时精度日期格式
	 * 
	 * @since 1.0
	 */
	public final static String DEFAULT_TO_HOUR_FORMAT = "yyyy-MM-dd HH";
	/**
	 * 默认分精度日期格式
	 * 
	 * @since 1.0
	 */
	public final static String DEFAULT_TO_MINUTE_FORMAT = "yyyy-MM-dd HH:mm";
	/**
	 * 默认秒精度日期格式
	 * 
	 * @since 1.0
	 */
	public final static String DEFAULT_TO_SECOND_FORMAT = "yyyy-MM-dd HH:mm:ss";
	/**
	 * 默认毫秒精度日期格式
	 * 
	 * @since 1.0
	 */
	public final static String DEFAULT_TO_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";

	/**
	 * 本地不带时区日期格式 'yyyy-MM-dd HH:mm:ss';
	 * 
	 * @since 1.0
	 */
	public final static DateTimeFormatter ISO_LOCAL_DATE_TIME_CN = new DateTimeFormatterBuilder().parseCaseInsensitive()
			.append(DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral(' ').append(DateTimeFormatter.ISO_LOCAL_TIME)
			.toFormatter();
	/**
	 * 本地不带时区日期格式 'yyyy-MM-dd' | 'yyyy-MM-dd HH:mm:ss';
	 * 
	 * @since 1.0
	 */
	public final static DateTimeFormatter ISO_LOCAL_DATE_TIME_EX = new DateTimeFormatterBuilder().parseCaseInsensitive()
			.append(DateTimeFormatter.ISO_LOCAL_DATE).optionalStart().appendLiteral(' ')
			.appendValue(ChronoField.HOUR_OF_DAY, 2).optionalStart().appendLiteral(':')
			.appendValue(ChronoField.MINUTE_OF_HOUR, 2).optionalStart().appendLiteral(':')
			.appendValue(ChronoField.SECOND_OF_MINUTE, 2).toFormatter();

	/**
	 * 本地不带时区简单日期格式 'yyyyMMdd';
	 */
	public static final DateTimeFormatter CNN_LOCAL_DATE = new DateTimeFormatterBuilder()
//			.appendValue(YEAR, 4, 10, SignStyle.NEVER) // 年，占位4~10
			.appendValue(YEAR, 4) // 年，占位2
			.appendValue(MONTH_OF_YEAR, 2) // 月，占位2
			.appendValue(DAY_OF_MONTH, 2) // 日，占位2
			.toFormatter();

	/**
	 * 本地不带时区简单时间格式 'HHmmss';
	 */
	public static final DateTimeFormatter CNN_LOCAL_TIME = new DateTimeFormatterBuilder()
//			.appendValue(ChronoField.HOUR_OF_DAY, 1, 2, SignStyle.NEVER) // 时，占位1~2
			.appendValue(ChronoField.HOUR_OF_DAY, 2) // 时，占位2
			.appendValue(ChronoField.MINUTE_OF_HOUR, 2) // 分，占位2
			.appendValue(ChronoField.SECOND_OF_MINUTE, 2) // 秒，占位2
			.toFormatter();

	/**
	 * 本地不带时区简单日期时间格式 'yyyyMMddHHmmss';
	 */
	public static final DateTimeFormatter CNN_LOCAL_DATETIME = new DateTimeFormatterBuilder() //
			.appendValue(YEAR, 4) // 年，占位2
			.appendValue(MONTH_OF_YEAR, 2) // 月，占位2
			.appendValue(DAY_OF_MONTH, 2) // 日，占位2
			.appendValue(ChronoField.HOUR_OF_DAY, 2) // 时，占位2
			.appendValue(ChronoField.MINUTE_OF_HOUR, 2) // 分，占位2
			.appendValue(ChronoField.SECOND_OF_MINUTE, 2) // 秒，占位2
			.toFormatter();

	/**
	 * 时区ID构建时区偏移量
	 * 
	 * @param zoneId 时区，非空值。
	 * @return 时区偏移量，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static ZoneOffset toZoneOffset(ZoneId zoneId) {
		Objects.requireNonNull(zoneId);
		if (zoneId instanceof ZoneOffset) {
			return (ZoneOffset) zoneId;
		}
		return zoneId.getRules().getOffset(Instant.EPOCH);
	}

	/**
	 * 时区对象构建时区偏移量
	 * 
	 * @param timeZone 时区，非空值。
	 * @return 时区偏移量，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static ZoneOffset toZoneOffset(TimeZone timeZone) {
		return toZoneOffset(timeZone.toZoneId());
	}

	/**
	 * 系统日历是否周末
	 * 
	 * @param calendar 系统日历，非空值。
	 * @return 周末返回真，否则返回假。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static boolean isWeekend(Calendar calendar) {
		Objects.requireNonNull(calendar);
		int st = calendar.get(Calendar.DAY_OF_WEEK);
		return st == Calendar.SATURDAY || st == Calendar.SUNDAY;
	}

	/**
	 * 本地日期是否周末
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @return 周末返回真，否则返回假。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static boolean isWeekend(LocalDate localDate) {
		Objects.requireNonNull(localDate);
		DayOfWeek st = localDate.getDayOfWeek();
		return DayOfWeek.SATURDAY == st || DayOfWeek.SUNDAY == st;
	}

	/**
	 * 本地日期所在周的周一
	 * 
	 * @param localDate 本地日期
	 * @return 所在周的周一
	 */
	public static LocalDate toWeekMonday(LocalDate localDate) {
		Objects.requireNonNull(localDate);
		DayOfWeek st = localDate.getDayOfWeek();
		int offset = st.ordinal() - DayOfWeek.MONDAY.ordinal();
		return offset == 0 ? localDate : localDate.minusDays(offset);
	}

	/**
	 * 本地日期所在周的周五
	 * 
	 * @param localDate 本地日期
	 * @return 所在周的周五
	 */
	public static LocalDate toWeekFriday(LocalDate localDate) {
		Objects.requireNonNull(localDate);
		DayOfWeek st = localDate.getDayOfWeek();
		int offset = st.ordinal() - DayOfWeek.FRIDAY.ordinal();
		return offset == 0 ? localDate : localDate.minusDays(offset);
	}

	/**
	 * 无时区时间戳转换为时区日期
	 * 
	 * @param millis 无时区时间戳
	 * @param offset 时区偏移量，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static LocalDate toLocalDate(long millis, ZoneOffset offset) {
		Objects.requireNonNull(offset, "offset");
		long localSecond = Math.floorDiv(millis, 1000) + offset.getTotalSeconds();
		long localEpochDay = Math.floorDiv(localSecond, SECONDS_PER_DAY);
		return LocalDate.ofEpochDay(localEpochDay);
	}

	/**
	 * 无时区时间戳转换为默认时区日期
	 * 
	 * @param millis 无时区时间戳
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toLocalDate(long, ZoneOffset)
	 */
	public static LocalDate toLocalDate(long millis) {
		return toLocalDate(millis, defaultZoneOffset);
	}

	/**
	 * 无时区时间戳转换为时区日期时间
	 * 
	 * @param millis 无时区时间戳
	 * @param offset 时区偏移量，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static LocalDateTime toLocalDateTime(long millis, ZoneOffset offset) {
		Objects.requireNonNull(offset, "offset");
		// return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), offset);
		long localSecond = Math.floorDiv(millis, 1000);
		int localNano = (int) Math.floorMod(millis, 1000) * 1000_000;
		return LocalDateTime.ofEpochSecond(localSecond, localNano, offset);
	}

	/**
	 * 无时区时间戳转换为默认时区日期时间
	 * 
	 * @param millis 无时区时间戳
	 * @return 时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toLocalDateTime(long, ZoneOffset)
	 */
	public static LocalDateTime toLocalDateTime(long millis) {
		return toLocalDateTime(millis, defaultZoneOffset);
	}

	/**
	 * 无时区日期时间转换为时区日期
	 * 
	 * @param date   无时区日期时间，非空值。
	 * @param offset 时区偏移量，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static LocalDate toLocalDate(Date date, ZoneOffset offset) {
		return toLocalDate(date.getTime(), offset);
	}

	/**
	 * 无时区日期时间转换为默认时区日期
	 * 
	 * @param date 无时区日期时间，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toLocalDate(Date, ZoneOffset)
	 */
	public static LocalDate toLocalDate(Date date) {
		return toLocalDate(date.getTime(), defaultZoneOffset);
	}

	/**
	 * 无时区日期时间转换为时区日期
	 * 
	 * @param date   无时区日期时间，非空值。
	 * @param zoneId 时区ID，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static LocalDate toLocalDate(Date date, ZoneId zoneId) {
		return toLocalDate(date.getTime(), toZoneOffset(zoneId));
	}

//	public static LocalDate toLocalDate(Date date, ZoneId zoneId) {
//		// JDK1.9才新增方法
//		return LocalDate.ofInstant(Instant.ofEpochMilli(date.getTime()), zoneId);
//	}

	/**
	 * 无时区日期时间转换为时区日期时间
	 * 
	 * @param date   无时区日期时间，非空值。
	 * @param offset 时区偏移量，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static LocalDateTime toLocalDateTime(Date date, ZoneOffset offset) {
		return toLocalDateTime(date.getTime(), offset);
	}

	/**
	 * 无时区日期时间转换为默认时区日期时间
	 * 
	 * @param date 无时区日期时间，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toLocalDateTime(Date, ZoneOffset)
	 */
	public static LocalDateTime toLocalDateTime(Date date) {
		return toLocalDateTime(date.getTime(), defaultZoneOffset);
	}

	/**
	 * 无时区本地日期换算时区秒数（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @param offset    时区偏移量，非空值
	 * @return 时区秒数
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static long toEpochSecond(LocalDate localDate, ZoneOffset offset) {
		Objects.requireNonNull(localDate, "date");
		Objects.requireNonNull(offset, "offset");
		long secs = localDate.toEpochDay() * SECONDS_PER_DAY;
		secs -= offset.getTotalSeconds();
		return secs;
	}

//	public static long toEpochSecond(LocalDate date, ZoneOffset offset) {
//		// JDK1.9才新增方法
//		return date.toEpochSecond(LocalTime.MIN, offset);
//	}

	/**
	 * 无时区本地日期换算默认时区秒数（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @return 时区秒数
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toEpochSecond(LocalDate)
	 */
	public static long toEpochSecond(LocalDate localDate) {
		return toEpochSecond(localDate, defaultZoneOffset);
	}

	/**
	 * 无时区本地时间换算时区秒数（秒精度）
	 * 
	 * @param localDateTime 无时区日期时间，非空值。
	 * @param offset        时区偏移量，非空值。
	 * @return 时区秒数
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static long toEpochSecond(LocalDateTime localDateTime, ZoneOffset offset) {
		Objects.requireNonNull(localDateTime, "date");
		Objects.requireNonNull(offset, "offset");
		return localDateTime.toEpochSecond(offset);
	}

	/**
	 * 无时区本地时间换算默认时区秒数（秒精度）
	 * 
	 * @param localDateTime 无时区日期时间，非空值。
	 * @return 时区秒数
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toEpochSecond(LocalDateTime, ZoneOffset)
	 */
	public static long toEpochSecond(LocalDateTime localDateTime) {
		return toEpochSecond(localDateTime, defaultZoneOffset);
	}

	/**
	 * 无时区本地日期换算时区毫秒数（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @param offset    时区ID，非空值。
	 * @return 时区毫秒数
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static long toEpochMilli(LocalDate localDate, ZoneOffset offset) {
		return toEpochSecond(localDate, offset) * 1000;
	}

	/**
	 * 无时区本地日期换算默认时区毫秒数（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @return 时区毫秒数
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toEpochMilli(LocalDate, ZoneOffset)
	 */
	public static long toEpochMilli(LocalDate localDate) {
		return toEpochSecond(localDate, defaultZoneOffset) * 1000;
	}

	/**
	 * 无时区本地时间换算时区毫秒数（毫秒精度）
	 * 
	 * @param localDateTime 无时区日期时间，非空值。
	 * @param offset        时区ID，非空值。
	 * @return 时区毫秒数
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static long toEpochMilli(LocalDateTime localDateTime, ZoneOffset offset) {
		return toEpochSecond(localDateTime, offset) * 1000;
	}

	/**
	 * 无时区本地时间换算默认时区毫秒数（毫秒精度）
	 * 
	 * @param localDateTime 无时区日期时间，非空值。
	 * @return 时区毫秒数
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toEpochMilli(LocalDateTime, ZoneOffset)
	 */
	public static long toEpochMilli(LocalDateTime localDateTime) {
		return toEpochSecond(localDateTime, defaultZoneOffset) * 1000;
	}

	/**
	 * 无时区系统毫秒数换算时区毫秒数（天精度）
	 * 
	 * @param millis 系统毫秒数，非负数。
	 * @param offset 时区偏移量，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(long millis, ZoneOffset offset) {
		Objects.requireNonNull(offset, "offset");
		long offsetMillis = offset.getTotalSeconds() * MILLISECONDS_PER_SECOND;
		return Math.floorDiv(millis + offsetMillis, MILLISECONDS_PER_DAY) * MILLISECONDS_PER_DAY - offsetMillis;
	}

	/**
	 * 无时区系统毫秒数换算默认时区毫秒数（天精度）
	 * 
	 * @param millis 系统毫秒数，非负数。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 * @see #toDayEpochMilli(long, ZoneOffset)
	 */
	public static long toDayEpochMilli(long millis) {
		long offsetMillis = defaultZoneOffset.getTotalSeconds() * MILLISECONDS_PER_SECOND;
		return Math.floorDiv(millis + offsetMillis, MILLISECONDS_PER_DAY) * MILLISECONDS_PER_DAY - offsetMillis;
	}

	/**
	 * 无时区日期时间换算时区毫秒数（天精度）
	 * 
	 * @param date   系统时间，非空值。
	 * @param offset 时区偏移量，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(Date date, ZoneOffset offset) {
		return toDayEpochMilli(date.getTime(), offset);
	}

	/**
	 * 无时区日期时间换算默认时区毫秒数（天精度）
	 * 
	 * @param date 系统时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(Date date) {
		return toDayEpochMilli(date.getTime(), defaultZoneOffset);
	}

	/**
	 * 无时区日期时间换算时区毫秒数（天精度）
	 * 
	 * @param date   系统时间，非空值。
	 * @param zoneId 时区ID，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(Date date, ZoneId zoneId) {
		return toDayEpochMilli(date.getTime(), toZoneOffset(zoneId));
	}

	/**
	 * 无时区日期时间换算时区毫秒数（天精度）
	 * 
	 * @param date 系统时间，非空值。
	 * @param zone 时区，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(Date date, TimeZone zone) {
		Objects.requireNonNull(zone);
		Calendar calendar = Calendar.getInstance(zone);
		calendar.setTime(date);
		calendar.set(Calendar.MILLISECOND, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		return calendar.getTimeInMillis();
	}

	/**
	 * 无时区本地日期换算时区毫秒数（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @param offset    时区偏移量，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(LocalDate localDate, ZoneOffset offset) {
		return toEpochMilli(localDate, offset);
	}

	/**
	 * 无时区本地日期换算默认时区毫秒数（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(LocalDate localDate) {
		return toEpochMilli(localDate, defaultZoneOffset);
	}

	/**
	 * 无时区本地日期换算时区毫秒数（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @param zoneId    时区ID，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(LocalDate localDate, ZoneId zoneId) {
		return toEpochMilli(localDate, toZoneOffset(zoneId));
	}

	/**
	 * 无时区本地日期换算时区毫秒数（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @param zone      时区，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 天精度截断后系统毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(LocalDate localDate, TimeZone zone) {
		return toEpochMilli(localDate, toZoneOffset(zone));
	}

	/**
	 * 无时区本地时间换算时区毫秒数（天精度）
	 * 
	 * @param localDate 无时区日期时间，非空值。
	 * @param offset    时区偏移量，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(LocalDateTime localDateTime, ZoneOffset offset) {
		return toEpochMilli(localDateTime.toLocalDate(), offset);
	}

	/**
	 * 无时区本地时间换算默认时区毫秒数（天精度）
	 * 
	 * @param localDate 无时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toDayEpochMilli(LocalDateTime localDateTime) {
		return toEpochMilli(localDateTime.toLocalDate(), defaultZoneOffset);
	}

	/**
	 * 无时区日期时间转换为时区日期（天精度）
	 * 
	 * @param date   系统时间，非空值。
	 * @param offset 时区偏移量，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static java.sql.Date toZoneDate(Date date, ZoneOffset offset) {
		long millis = toDayEpochMilli(date.getTime(), offset);
		if (millis == date.getTime() && date instanceof java.sql.Date) {
			return (java.sql.Date) date;
		}
		return new java.sql.Date(millis);
	}

	/**
	 * 无时区日期时间转换为默认时区日期（天精度）
	 * 
	 * @param date 系统时间，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static java.sql.Date toZoneDate(long millis) {
		return toZoneDate(millis, defaultZoneOffset);
	}

	/**
	 * 无时区日期转换为时区日期（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @param offset    时区偏移量，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空参数值为空
	 * @since 1.0
	 */
	public static java.sql.Date toZoneDate(long millis, ZoneOffset offset) {
		return new java.sql.Date(toDayEpochMilli(millis, offset));
	}

	/**
	 * 无时区日期时间转换为默认时区日期（天精度）
	 * 
	 * @param date 系统时间，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static java.sql.Date toZoneDate(Date date) {
		return toZoneDate(date, defaultZoneOffset);
	}

	/**
	 * 无时区日期转换为时区日期（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @param offset    时区偏移量，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空参数值为空
	 * @since 1.0
	 */
	public static java.sql.Date toZoneDate(LocalDate localDate, ZoneOffset offset) {
		return new java.sql.Date(toEpochMilli(localDate, offset));
	}

	/**
	 * 无时区日期转换为默认时区日期（天精度）
	 * 
	 * @param localDate 无时区日期，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空参数值为空
	 * @since 1.0
	 * @see #toZoneDate(LocalDate, ZoneOffset)
	 */
	public static java.sql.Date toZoneDate(LocalDate localDate) {
		return new java.sql.Date(toEpochMilli(localDate, defaultZoneOffset));
	}

	/**
	 * 无时区日期时间转换为时区日期（天精度）
	 * 
	 * @param localDateTime 无时区日期时间，非空值。
	 * @param offset        时区偏移量，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static java.sql.Date toZoneDate(LocalDateTime localDateTime, ZoneOffset offset) {
		return new java.sql.Date(toEpochMilli(localDateTime.toLocalDate(), offset));
	}

	/**
	 * 无时区日期时间转换为默认时区日期（天精度）
	 * 
	 * @param localDateTime 无时区日期时间，非空值。
	 * @return 时区日期，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toZoneDate(LocalDateTime, ZoneOffset)
	 */
	public static java.sql.Date toZoneDate(LocalDateTime localDateTime) {
		return new java.sql.Date(toEpochMilli(localDateTime.toLocalDate(), defaultZoneOffset));
	}

	/**
	 * 无时区日期时间转换为时区日期（天精度）
	 * 
	 * @param localDateTime 无时区日期，非空值。
	 * @param offset        时区偏移量，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static java.sql.Timestamp toZoneTimestamp(LocalDate localDate, ZoneOffset offset) {
		return new java.sql.Timestamp(toEpochMilli(localDate, offset));
	}

	/**
	 * 无时区日期转换为默认时区日期时间（天精度）
	 * 
	 * @param localDateTime 无时区日期，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toZoneDate(LocalDateTime, ZoneOffset)
	 */
	public static java.sql.Timestamp toZoneTimestamp(LocalDate localDate) {
		return new java.sql.Timestamp(toEpochMilli(localDate, defaultZoneOffset));
	}

	/**
	 * 无时区日期时间转换为时区日期时间（毫秒精度）
	 * 
	 * @param localDateTime 无时区日期时间，非空值。
	 * @param offset        时区偏移量，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static java.sql.Timestamp toZoneTimestamp(LocalDateTime localDateTime, ZoneOffset offset) {
		return new java.sql.Timestamp(toEpochMilli(localDateTime, offset));
	}

	/**
	 * 无时区日期时间转换为默认时区日期时间（毫秒精度）
	 * 
	 * @param localDateTime 无时区日期时间，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 * @see #toZoneDate(LocalDateTime, ZoneOffset)
	 */
	public static java.sql.Timestamp toZoneTimestamp(LocalDateTime localDateTime) {
		return new java.sql.Timestamp(toEpochMilli(localDateTime, defaultZoneOffset));
	}

	/**
	 * 无时区日期时间转换为时区日期时间（毫秒精度）
	 * 
	 * @param localDateTime 无时区日期时间，非空值。
	 * @param offset        时区偏移量，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @since 1.0
	 */
	public static java.sql.Timestamp toZoneTimestamp(Date datetime, ZoneOffset offset) {
		return new java.sql.Timestamp(toZoneMillis(datetime.getTime(), offset));
	}

	/**
	 * 无时区系统毫秒数换算时区毫秒数（毫秒精度）
	 * 
	 * @param millis 系统毫秒数，非负数。
	 * @param offset 时区偏移量，非空值。
	 * @throws NullPointerException 非空值参数为空值时
	 * @return 时区毫秒数
	 * @since 1.0
	 */
	public static long toZoneMillis(long millis, ZoneOffset offset) {
		Objects.requireNonNull(offset, "offset");
		return millis - offset.getTotalSeconds() * MILLISECONDS_PER_SECOND;
	}

	/**
	 * 解析时区日期（天精度）
	 * <ul>
	 * <li>'yyyy-MM-dd HH:mm:ss'</li>
	 * <li>'yyyy-MM-dd HH:mm'</li>
	 * <li>'yyyy-MM-dd HH'</li>
	 * <li>'yyyy-MM-dd'</li>
	 * </ul>
	 * 
	 * @param text   无时区日期时间，非空值。
	 * @param offset 时区偏移量，非空值。
	 * @return 时区日期，非空值。
	 * @throws DateTimeParseException 无效日期格式
	 * @throws NullPointerException   非空值参数为空值时
	 * @since 1.0
	 */
	public static java.sql.Date parseDate(String text, ZoneOffset offset) throws DateTimeParseException {
		return new java.sql.Date(toEpochMilli(LocalDate.parse(text, ISO_LOCAL_DATE_TIME_EX), offset));
	}

	/**
	 * 解析默认时区日期（天精度）
	 * <ul>
	 * <li>'yyyy-MM-dd HH:mm:ss'</li>
	 * <li>'yyyy-MM-dd HH:mm'</li>
	 * <li>'yyyy-MM-dd HH'</li>
	 * <li>'yyyy-MM-dd'</li>
	 * </ul>
	 * 
	 * @param text 无时区日期时间，非空值。
	 * @return 时区日期，非空值。
	 * @throws DateTimeParseException 无效日期格式
	 * @throws NullPointerException   非空值参数为空值时
	 * @since 1.0
	 * @see #toZoneDate(String, ZoneOffset)
	 */
	public static java.sql.Date parseDate(String text) throws DateTimeParseException {
		return new java.sql.Date(toEpochMilli(LocalDate.parse(text, ISO_LOCAL_DATE_TIME_EX), defaultZoneOffset));
	}

	/**
	 * 解析时区日期时间（毫秒精度），本地时间可选格式如下：
	 * <ul>
	 * <li>'yyyy-MM-dd HH:mm:ss'</li>
	 * <li>'yyyy-MM-dd HH:mm'</li>
	 * <li>'yyyy-MM-dd HH'</li>
	 * </ul>
	 * 
	 * @param text   无时区日期时间，非空值。
	 * @param offset 时区偏移量，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws DateTimeParseException 无效日期时间格式
	 * @throws NullPointerException   非空值参数为空值时
	 * @since 1.0
	 */
	public static java.sql.Timestamp parseTimestamp(String text, ZoneOffset offset) throws DateTimeParseException {
		return new java.sql.Timestamp(toEpochMilli(LocalDateTime.parse(text, ISO_LOCAL_DATE_TIME_EX), offset));
	}

	/**
	 * 解析时区日期时间（毫秒精度），本地时间可选格式如下：
	 * <ul>
	 * <li>'yyyy-MM-dd HH:mm:ss'</li>
	 * <li>'yyyy-MM-dd HH:mm'</li>
	 * <li>'yyyy-MM-dd HH'</li>
	 * </ul>
	 * 
	 * @param text   无时区日期时间，非空值。
	 * @param offset 时区偏移量，非空值。
	 * @return 时区日期时间，非空值。
	 * @throws DateTimeParseException 无效日期时间格式
	 * @throws NullPointerException   非空值参数为空值时
	 * @since 1.0
	 * @see #toZoneTimestamp(String, ZoneOffset)
	 */
	public static java.sql.Timestamp parseTimestamp(String text) throws DateTimeParseException {
		return new java.sql.Timestamp(
				toEpochMilli(LocalDateTime.parse(text, ISO_LOCAL_DATE_TIME_EX), defaultZoneOffset));
	}

}
